package dfs

/*
#cgo LDFLAGS: -ldaos -ldfs
#include <daos.h>
#include <daos_api.h>
#include <daos_fs.h>
*/
import "C"
import "unsafe"
import "fmt"


// DFS represents a DFS object
type DFS struct {
	dfs *C.dfs_t
}

// DFSObj represents a DFS object in Go, wrapping the C.dfs_obj_t type.
type DFSObj struct {
	obj *C.dfs_obj_t
}

// SGListT represents a scatter-gather list in Go, wrapping the C.d_sg_list_t type.
type SGListT struct {
	sgl_list C.d_sg_list_t
}

/** I/O descriptor for DFS object */
type IovT struct {
	iov C.d_iov_t
}

// DaosSizeT represents a daos_size_t type in Go.
type DaosSizeT struct {
	size C.daos_size_t
}

// DaosOffT represents a daos_off_t type in Go.
type DaosOffT struct {
	offset C.daos_off_t
}

// DaosEventT represents a daos_event_t type in Go.
type DaosEventT struct {
	event *C.daos_event_t
}

// DaosIodT represents a dfs_iod_t type in Go.
type DaosIodT struct {
	iod *C.dfs_iod_t
}

// DaosAttrT represents a dfs_attr_t type in Go.
type DaosAttrT struct {
	attr *C.dfs_attr_t
}

// EntryType represents the type of a directory entry
type EntryType int

const (
	EntryTypeUnknown EntryType = iota
	EntryTypeFile
	EntryTypeDirectory
	EntryTypeSymlink
)

// DirEntry represents a directory entry
type DirEntry struct {
	Name string
	Type EntryType
}

// NewSGListT creates a new scatter-gather list with the specified number of I/O vectors.
//
// Parameters:
//   - numIov: The number of I/O vectors to allocate in the scatter-gather list.
//
// Returns:
//   - A pointer to the newly created SGListT.
//   - An error if the allocation fails.
func NewSGListT(numIov int) (*SGListT, error) {
	sgl := &SGListT{}
	return sgl, nil
}


// NewIovT creates a new I/O vector with the specified buffer and size.
//
// Parameters:
//   - buf: Pointer to the buffer.
//   - size: Size of the buffer.
//
// Returns:
//   - A pointer to the newly created IovT.
func NewIovT(buf unsafe.Pointer, size uint64) *IovT {
	iov := &IovT{}
	C.d_iov_set(&iov.iov, buf, C.size_t(size))
	return iov
}


// IovSet sets the buffer and size of an I/O vector.

// Parameters:
//   - iov: Pointer to the I/O vector.
//   - buf: Pointer to the buffer.
//   - size: Size of the buffer.
//
// Returns:
//   - error: An error if the operation fails, otherwise nil.
func (d *DFS) IovSet(iov *IovT, buf unsafe.Pointer, size uint64) error {
	C.d_iov_set(&iov.iov, buf, C.size_t(size))
	return nil
}



// DaosSizeT represents a daos_size_t type in Go.

// Init initializes the DFS (Distributed File System) instance.
// It calls the underlying C function `dfs_init` to perform the initialization.
// If the initialization fails, it returns an error with the failure code.
// Returns nil if the initialization is successful.
 func (d *DFS) Init() error {
	rc := C.dfs_init()
	if rc != 0 {
		return fmt.Errorf("failed to initialize DFS: %d", rc)
	}
	return nil
}


// Fini finalizes the DFS instance, releasing any resources associated with it.
// It returns an error if the finalization process fails.
func (d *DFS) Fini() error {
	rc := C.dfs_fini()
	if rc != 0 {
		return fmt.Errorf("failed to finalize DFS: %d", rc)
	}
	return nil
}


// Connect establishes a connection to a DFS (Distributed File System) instance.
//
// Parameters:
//   - pool: The pool name to connect to.
//   - sys: The system name to connect to.
//   - cont: The container name to connect to.
//   - flags: Connection flags.
//   - attr: Pointer to DFS attributes.
//
// Returns:
//   - error: An error if the connection fails, otherwise nil.
func (d *DFS) Connect(pool, sys, cont string, flags int, attr *DaosAttrT) error {
	cPool := C.CString(pool)
	defer C.free(unsafe.Pointer(cPool))
	cSys := C.CString(sys)
	defer C.free(unsafe.Pointer(cSys))
	cCont := C.CString(cont)
	defer C.free(unsafe.Pointer(cCont))

	var cAttr *C.dfs_attr_t
	if attr != nil {
		cAttr = attr.attr
	}
	rc := C.dfs_connect(cPool, cSys, cCont, C.int(flags), cAttr, &d.dfs)
	if rc != 0 {
		return fmt.Errorf("failed to connect DFS: %d", rc)
	}
	return nil
}


// Disconnect disconnects the DFS instance. 
// It calls the underlying C function to perform the disconnection.
// If the disconnection fails, it returns an error with the failure code.
func (d *DFS) Disconnect() error {
	rc := C.dfs_disconnect(d.dfs)
	if rc != 0 {
		return fmt.Errorf("failed to disconnect DFS: %d", rc)
	}
	return nil
}


// Open opens a DFS object with the specified parameters.
//
// Parameters:
// - parent: A pointer to the parent DFS object.
// - name: The name of the DFS object to open.
// - mode: The mode in which to open the DFS object.
// - flags: Flags to control the behavior of the open operation.
// - cid: The object class ID for the DFS object.
// - chunkSize: The chunk size for the DFS object.
// - value: A string value associated with the DFS object.
//
// Returns:
// - A pointer to the opened DFS object.
// - An error if the operation fails.
func (d *DFS) Open(parent *DFSObj, name string, mode int, flags int, chunkSize uint64, value string) (*DFSObj, error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	// Convert the string value to a C string
    var cValue *C.char
    if value != "" {
        cValue = C.CString(value)
        defer C.free(unsafe.Pointer(cValue))
    }

	var obj *C.dfs_obj_t
	rc := C.dfs_open(d.dfs, parent.obj, cName, C.mode_t(mode), C.int(flags), 0, C.ulong(chunkSize), cValue, &obj)
	if rc != 0 {
		return nil, fmt.Errorf("failed to open DFS object: %d", rc)
	}
	return &DFSObj{obj: obj}, nil
}


// Mkdir creates a directory in the DFS.
//
// Parameters:
//   parent - Pointer to the parent DFS object.
//   name - The name of the directory to create.
//   mode - The mode in which to create the directory.
//   cid - The object class ID for the directory.
//
// Returns:
//   error - An error if the directory creation fails, otherwise nil.
func (d *DFS) Mkdir(parent *DFSObj, name string, mode int) error {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	rc := C.dfs_mkdir(d.dfs, parent.obj, cName, C.mode_t(mode), 0)
	if rc != 0 {
		return fmt.Errorf("failed to create directory: %d", rc)
	}
	return nil
}


// Read reads data from a DFS object into a scatter-gather list.
//
// Parameters:
//   obj - Pointer to the DFS object to read from.
//   sgl - A 2D byte slice where the read data will be stored.
//   off - Offset within the DFS object to start reading from.
//   len - Length of the data to be read.
//   ev - Pointer to the event structure for asynchronous operations.
//
// Returns:
//   error - An error if the read operation fails, otherwise nil.
func (d *DFS) Read(obj *DFSObj, sgl [][]byte, off uint64, ev *DaosEventT) (uint64, error) {
	// Create a scatter-gather list
	sglList := C.d_sg_list_t{}
	sglList.sg_nr = C.uint(len(sgl))
	sglList.sg_iovs = (*C.d_iov_t)(C.calloc(C.size_t(sglList.sg_nr), C.sizeof_d_iov_t))
	defer C.free(unsafe.Pointer(sglList.sg_iovs))
	for i := 0; i < len(sgl); i++ {
		iov := (*C.d_iov_t)(unsafe.Pointer(uintptr(unsafe.Pointer(sglList.sg_iovs)) + uintptr(i)*C.sizeof_d_iov_t))
		C.d_iov_set(iov, unsafe.Pointer(&sgl[i][0]), C.size_t(len(sgl[i])))
	}
	var evPtr *C.daos_event_t
	if ev != nil {
		evPtr = ev.event
	}

	read_len := C.daos_size_t(0)
	rc := C.dfs_read(d.dfs, obj.obj, &sglList, C.daos_off_t(off), &read_len, evPtr)
	if rc != 0 {
		return uint64(read_len), fmt.Errorf("failed to read from DFS object: %d", rc)
	}
	return uint64(read_len), nil
}

// Readx reads data from a DFS object using an IO descriptor.
//
// Parameters:
//   obj - Pointer to the DFS object to read from.
//   iod - Pointer to the IO descriptor of the file view.
//   sgl - Pointer to the scatter-gather list where the read data will be stored.
//   readSize - Pointer to the size of the data to be read.
//   ev - Pointer to the event structure for asynchronous operations.
//
// Returns:
//   error - An error if the read operation fails, otherwise nil.
func (d *DFS) Readx(obj *DFSObj, iod *DaosIodT, sgl *SGListT, readSize *DaosSizeT, ev *DaosEventT) error {
	rc := C.dfs_readx(d.dfs, obj.obj, iod.iod, &sgl.sgl_list, &readSize.size, ev.event)
	if rc != 0 {
		return fmt.Errorf("failed to read from DFS object: %d", rc)
	}
	return nil
}

// Write writes data to a DFS object from a scatter-gather list.
//
// Parameters:
//   obj - Pointer to the DFS object to write to.
//   sgl - Pointer to the scatter-gather list containing the data to be written.
//   off - Offset within the DFS object to start writing to.
//   ev - Pointer to the event structure for asynchronous operations.
//
// Returns:
//   error - An error if the write operation fails, otherwise nil.
func (d *DFS) Write(obj *DFSObj, sgl [][]byte, off uint64, ev *DaosEventT) error {
	// Create a scatter-gather list
	sglList := C.d_sg_list_t{}
	sglList.sg_nr = C.uint(len(sgl))
	sglList.sg_iovs = (*C.d_iov_t)(C.calloc(C.size_t(sglList.sg_nr), C.sizeof_d_iov_t))
	defer C.free(unsafe.Pointer(sglList.sg_iovs))
	for i := 0; i < len(sgl); i++ {
		iov := (*C.d_iov_t)(unsafe.Pointer(uintptr(unsafe.Pointer(sglList.sg_iovs)) + uintptr(i)*C.sizeof_d_iov_t))
		C.d_iov_set(iov, unsafe.Pointer(&sgl[i][0]), C.size_t(len(sgl[i])))
	}

	// Set the event pointer
	var evPtr *C.daos_event_t
	if ev != nil {
		evPtr = ev.event
	}

	rc := C.dfs_write(d.dfs, obj.obj, &sglList, C.daos_off_t(off), evPtr)
	if rc != 0 {
		return fmt.Errorf("failed to write to DFS object: %d", rc)
	}
	
	return nil
}


// Writex writes data to a DFS object from a scatter-gather list using an IO descriptor.
//
// Parameters:
//   obj - Pointer to the DFS object to write to.
//   iod - Pointer to the IO descriptor of the file view.
//   sgl - Pointer to the scatter-gather list containing the data to be written.
//   ev - Pointer to the event structure for asynchronous operations.
//
// Returns:
//   error - An error if the write operation fails, otherwise nil.
func (d *DFS) Writex(obj *DFSObj, iod *DaosIodT, sgl *SGListT, ev *DaosEventT) error {
	rc := C.dfs_writex(d.dfs, obj.obj, iod.iod, &sgl.sgl_list, ev.event)
	if rc != 0 {
		return fmt.Errorf("failed to write to DFS object: %d", rc)
	}
	return nil
}


// Release releases a DFS object.
//
// Parameters:
//   obj - A pointer to the DFS object to be released.
//
// Returns:
//   An error if the release operation fails, otherwise nil.
func (d *DFS) Release(obj *DFSObj) error {
	rc := C.dfs_release(obj.obj)
	if rc != 0 {
		return fmt.Errorf("failed to release DFS object: %d", rc)
	}
	return nil
}

// ListDir lists all entries in a directory.
//
// Parameters:
//   - dir: Pointer to the directory DFS object
//   - anchor: Optional anchor for pagination (can be nil)
//
// Returns:
//   - []DirEntry: Slice of directory entries
//   - error: An error if the operation fails
func (d *DFS) ListDir(dir *DFSObj) ([]DirEntry, error) {
	var entries []DirEntry
	
	// Create callback function
	callback := func(parent *C.dfs_obj_t, name *C.char, type_ C.mode_t, _ unsafe.Pointer) C.int {
		entry := DirEntry{
			Name: C.GoString(name),
		}
		
		// Determine entry type
		switch {
		case type_&C.S_IFREG != 0:
			entry.Type = EntryTypeFile
		case type_&C.S_IFDIR != 0:
			entry.Type = EntryTypeDirectory
		case type_&C.S_IFLNK != 0:
			entry.Type = EntryTypeSymlink
		default:
			entry.Type = EntryTypeUnknown
		}
		
		entries = append(entries, entry)
		return 0
	}

	// Convert callback to C function pointer
	var cb C.dfs_iter_cb_t = (C.dfs_iter_cb_t)(unsafe.Pointer(&callback))

	// Call dfs_iterate      
	rc := C.dfs_iterate(d.dfs, dir.obj, nil, cb, nil)
	if rc != 0 {
		return nil, fmt.Errorf("failed to iterate directory: %d", rc)
	}

	return entries, nil
}